# frozen_string_literal: true

require 'set'
require 'jazzy/symbol_graph/graph'
require 'jazzy/symbol_graph/constraint'
require 'jazzy/symbol_graph/symbol'
require 'jazzy/symbol_graph/relationship'
require 'jazzy/symbol_graph/sym_node'
require 'jazzy/symbol_graph/ext_node'
require 'jazzy/symbol_graph/ext_key'

# This is the top-level symbolgraph driver that deals with
# figuring out arguments, running the tool, and loading the
# results.

module Jazzy
  module SymbolGraph
    # Find swift symbol graph files, either having been passed
    # in directly, or generated by running`swift symbolgraph-extract`
    # with configured args.
    # Then parse the results, and return as JSON in SourceKit[ten]
    # format.
    def self.build(module_config)
      if module_config.symbolgraph_directory.nil?
        Dir.mktmpdir do |tmp_dir|
          args = arguments(module_config, tmp_dir)

          Executable.execute_command('swift',
                                     args.unshift('symbolgraph-extract'),
                                     true) # raise on error

          parse_symbols(tmp_dir)
        end
      else
        parse_symbols(module_config.symbolgraph_directory.to_s)
      end
    end

    # Figure out the args to pass to symbolgraph-extract
    def self.arguments(module_config, output_path)
      if module_config.module_name.empty?
        raise 'error: `--swift-build-tool symbolgraph` requires `--module`.'
      end

      user_args = module_config.build_tool_arguments.join

      if user_args =~ /-(?:module-name|minimum-access-level|output-dir)/
        raise 'error: `--build-tool-arguments` for ' \
          "`--swift-build-tool symbolgraph` can't use `-module`, " \
          '`-minimum-access-level`, or `-output-dir`.'
      end

      # Default set
      args = [
        '-module-name', module_config.module_name,
        '-minimum-access-level', 'private',
        '-output-dir', output_path,
        '-skip-synthesized-members'
      ]

      # Things user can override
      args += ['-sdk', sdk(module_config)] unless user_args =~ /-sdk/
      args += ['-target', target] unless user_args =~ /-target/
      args += ['-F', module_config.source_directory.to_s] unless user_args =~ /-F(?!s)/
      args += ['-I', module_config.source_directory.to_s] unless user_args =~ /-I/

      args + module_config.build_tool_arguments
    end

    # Parse the symbol files in the given directory
    def self.parse_symbols(directory)
      Dir[directory + '/*.symbols.json'].sort.map do |filename|
        # The @ part is for extensions in our module (before the @)
        # of types in another module (after the @).
        File.basename(filename) =~ /(.*?)(@(.*?))?\.symbols/
        module_name = Regexp.last_match[1]
        ext_module_name = Regexp.last_match[3] || module_name
        json = File.read(filename)
        {
          filename =>
            Graph.new(json, module_name, ext_module_name).to_sourcekit,
        }
      end.to_json
    end

    # Get the SDK path.  On !darwin this just isn't needed.
    def self.sdk(module_config)
      `xcrun --show-sdk-path --sdk #{module_config.sdk}`.chomp
    end

    # Guess a default LLVM target.  Feels like the tool should figure this
    # out from sdk + the binary somehow?
    def self.target
      `swift -version` =~ /Target: (.*?)$/
      Regexp.last_match[1] || 'x86_64-apple-macosx10.15'
    end

    # This is a last-ditch fallback for when symbolgraph doesn't
    # provide a name - at least conforming external types to local
    # protocols.
    def self.demangle(usr)
      args = %w[demangle -simplified -compact].append(usr.sub(/^s:/, 's'))
      output, = Executable.execute_command('swift', args, true)
      output.chomp
    rescue StandardError
      usr
    end
  end
end
